<?php
/**
 *
 *
 *
 */

class Yz_String extends Yz_Class
{

    // NOTE for our purposes, do not consider underscore as valid symbol in an identifier
    const IDENTIFIER = '~^[a-z][0-9a-z]*$~i';

    /** valueOf(): whether the value is usable in string context
     *      It requires a scalar type except boolean
    *       Objects (even if they have toString()) do not count as such,
    *       neither do arrays, resources and null
    *   @param scalar value
    *   @return string|null
    */
    public static function valueOf( $value )
    {
        switch( true ) {
            case null === $value:
            case true === $value:   // not a valid string
            case false === $value:  // neither is this
                $value = null;
                break;
            case is_scalar($value): // int, float, string ?
                $value = "$value";
                break;
            default:
                $value = null;
        } // switch

        return $value;
    } // valueOf

    /** str(): string representation of the value
     *      For arrays, dumb objects and resources, their type is returned
     *   @param mixed value
     *   @return string
     */
    public static function str( $value )
    {
        switch( true ) {
            case null === $value:
                $value = 'null';
                break;
            case true === $value:
                $value = 'true';
                break;
            case false === $value:
                $value = 'false';
                break;
            case $value instanceof Yz_Class:
            case is_scalar($value): // "null" is not considered scalar
                $value = "$value";
                break;
            default:
                $value = gettype($value);
        } // switch

        return $value;
    } // valueOf



    /** html representation of the value
     *  encode <>"'&\
     *   skip already encoded entities
     *   before replace, delete all non-printable chars (those before space - range 0x00:0x1F)
     *  @param string
     *  @return string
     */
    public static function toHTML( $value )
    {
        if ( null === $value or "" === $value )
            return "";

        $value = static::str( $value );

        $regexp = <<<cc
~([<>"'\\\\])|(&(?!(#x[a-f0-9]{1,4})|(#\d{1,5})|([a-z]{1,6});))~i
cc;
        $src = preg_replace( '~[\x00-\x09\x0B-\x1F]~', ' ', $value );
        $dest = preg_replace_callback(
                $regexp,
                function( $matches ) {
                    $c = ord($matches[0]);
                    return "&#$c;";
                },
                $src
        );
        return $dest;
    }



    /** convert string converted by toHTML() to plain text: replace entities
     *  @param string
     *  @return string
     */
    public static function fromHTML($value)
    {
        throw NotImplementedError::_new_();
    }


    /** isTrue() : whether a value's string representation can be interpreted as boolean:
     *      yes/no, on/off, true/false, enabled/disabled
     *  @param string
     *  @return bool/null
     */
    public static function isTrue( $value )
    {
        if ( null === $value or true === $value or false === $value )
            return $value;
        $value = static::str( $value );

/*
 * tests showed this 2x slower than switch
        $r = preg_match("~^(true|yes|on|enabled)|(false|no|off|disabled)$~i", $value, $m);
        if (! $r) {
            return null;
        }
        if (isset($m[2])) {
            return false;
        }
        return true;
*/
        // do not use mb_
        switch( strtolower($value) ) {
          case "on":
          case "true":
          case "enabled":
          case "yes":
            $r = true;
            break;
          case "off":
          case "false":
          case "disabled":
          case "no":
            $r = false;
            break;
          default:
            $r = $value;
        } // switch
        return $r;
    } // isTrue



    /** Replace (case-insensitive) variables %{var} in string
    *   @param string parameterized_string
    *   @param array-type vars
    *   @return string
    */
    public static function replaceVars( $str, $vars )
    {
        $str = self::test( $str, 'Parameterized string' );
        $vars = Yz_Hash::test($vars, 'replacement parameters');
        if ("" !== $str)
            foreach ( $vars as $k => $v )
                $str = str_ireplace( '%{' .$k .'}', self::str($v), $str );

        return $str;
    } // replaceVars


    /** check whether the string is a valid identifier
     *  @param string
     *  @return bool
     */
    public static function isValidIdentifier( $str )
    {
        return (bool)preg_match( Yz_String::IDENTIFIER, $str );
    }

    /** translate a-long-CaSe-iNseNsitive-string to aLongCaseInsensitiveString
     *  @param string
     *  @return string
     *  @throw InvalidValueType
     */
    public static function toCamelCase( $str )
    {
        $str = self::test($str);
        $str = preg_replace_callback(
            '~[\s._-]+([a-z]?)~',
            function($m) {
                if ($m[1])
                    return strtoupper($m[1]);
                return '';
            },
            strtolower($str)
        );
        return $str;
    }

    /** translate aLongCaseSensitiveString to a-long-case-sensitive-string
     *  @param string
     *  @return string
     *  @throw InvalidValueType
     */
    public static function fromCamelCase( $str )
    {
        $str = self::test($str);
        $str = preg_replace_callback(
            '~[A-Z]~',
            function($m) {
                return '-' . strtolower($m[0]);
            },
            lcfirst($str) // ensure that the first char is not uppercase -> "-"
        );
        return $str;
    }



} // class

